/*
 * Copyright (C) 2015, apexes.net. All rights reserved.
 * 
 *        http://www.apexes.net
 * 
 */
package net.apexes.wsonrpc.client.support;

import net.apexes.wsonrpc.client.WebsocketConnector;
import net.apexes.wsonrpc.client.WsonrpcClientEndpoint;
import net.apexes.wsonrpc.core.WebSocketSession;
import org.java_websocket.WebSocket;
import org.java_websocket.client.WebSocketClient;
import org.java_websocket.drafts.Draft_6455;
import org.java_websocket.enums.ReadyState;
import org.java_websocket.framing.Framedata;
import org.java_websocket.framing.PingFrame;
import org.java_websocket.handshake.ServerHandshake;

import java.io.IOException;
import java.net.URI;
import java.nio.ByteBuffer;
import java.nio.charset.StandardCharsets;
import java.util.UUID;

/**
 * 基于 {@link org.java_websocket.client.WebSocketClient}的连接
 * 
 * @author <a href="mailto:hedyn@foxmail.com">HeDYn</a>
 *
 */
public class JavaWebsocketConnector implements WebsocketConnector {

    private final int connectTimeout;

    public JavaWebsocketConnector() {
        this(2000);
    }

    public JavaWebsocketConnector(int connectTimeout) {
        this.connectTimeout = connectTimeout;
    }
    
    @Override
    public void connectToServer(WsonrpcClientEndpoint endpoint, URI uri) throws Exception {
        WebSocketClientAdapter clientAdapter = new WebSocketClientAdapter(uri, endpoint, connectTimeout);
        clientAdapter.connectToServer();
    }
    
    /**
     * 
     * @author <a href="mailto:hedyn@foxmail.com">HeDYn</a>
     *
     */
    private static class WebSocketClientAdapter extends WebSocketClient implements WebSocketSession {
        
        private static final PingFrame PING_FRAME = new PingFrame();

        private final WsonrpcClientEndpoint endpoint;
        private Exception connectError;
        private String id;
        private volatile boolean opened;
        private boolean connecting = false;
        
        public WebSocketClientAdapter(URI uri, WsonrpcClientEndpoint endpoint, int connectTimeout) {
            super(uri, new Draft_6455(), null, connectTimeout);
            this.endpoint = endpoint;
        }

        void connectToServer() throws Exception {
            connectError = null;
            connecting = true;
            connectBlocking();
            connecting = false;
            if (connectError != null) {
                throw connectError;
            }
        }

        @Override
        public void onOpen(ServerHandshake handshakedata) {
            id = UUID.randomUUID().toString();
            opened = true;
            endpoint.onOpen(this);
        }

        @Override
        public void onMessage(String message) {
            endpoint.onMessage(message.getBytes(StandardCharsets.UTF_8));
        }
        
        @Override
        public void onMessage(ByteBuffer bytes) {
            endpoint.onMessage(bytes.array());
        }

        @Override
        public void onClose(int code, String reason, boolean remote) {
            if (!connecting) {
                opened = false;
                endpoint.onClose(code, reason);
            }
        }

        @Override
        public void onError(Exception error) {
            if (getConnection().getReadyState() == ReadyState.NOT_YET_CONNECTED) {
                this.connecting = true;
                this.connectError = error;
            } else {
                this.endpoint.onError(error);
            }
        }

        @Override
        public void onWebsocketPong(WebSocket conn, Framedata f ) {
            endpoint.onPong(f.getPayloadData().array());
        }
        
        @Override
        public String getId() {
            return id;
        }

        @Override
        public boolean isOpen() {
            return opened;
        }

        @Override
        public void sendBinary(byte[] bytes) throws IOException {
            send(bytes);
        }

        @Override
        public void ping(byte[] bytes) throws IOException {
            PingFrame frame;
            if (bytes == null || bytes.length == 0) {
                frame = PING_FRAME;
            } else {
                frame = new PingFrame();
                frame.setPayload(ByteBuffer.wrap(bytes));
            }
            getConnection().sendFrame(frame);
        }
        
    }

}
